package com.blog.common.utils;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.Assert;


/**
 * 分布式锁，工具包
 * @author marui
 *
 * 使用方法，通过@Autowired注入DistributedLockUtil，然后lock方法锁定，unlock方法解锁
 */
public class DistributedLockUtil {
	
	//默认最大锁定时间5分钟
	private static long DEF_MAX_LOCK_TIME = 1000 * 60 * 5;
	
	//动态自适应锁定时间
	private static long dynamicLockTime = DEF_MAX_LOCK_TIME;
	
	//已锁标志，防止同一个程序在反复重复调用lock锁定函数又没有unlock
	private static volatile boolean isLocked = false;
	
	private static final String LOCK_KEY = "DISTRIBUTED_LOCK_KEY";
	
	private static Logger logger = LoggerFactory.getLogger(DistributedLockUtil.class);

	@Autowired
	private RedisUtils redis;
	
	
	/**
	 * 删除锁
	 * @param keyType
	 */
	private void delLock(String keyType) {
		keyType = keyType == null ? StringUtils.EMPTY : keyType;
		redis.del(LOCK_KEY + keyType);
	}
	
	/**
	 * 获得当前锁值
	 * @param keyType
	 * @return
	 */
	private String getLockValue(String keyType) {
		keyType = keyType == null ? StringUtils.EMPTY : keyType;
		
		return redis.get(LOCK_KEY + keyType, "NULL");
	}
	
	
	/**
	 * 设置锁值，一般为时间戳
	 * @param keyType 业务类型
	 * @param lockTime 锁值时间
	 * @return
	 */
	private boolean setLock(String keyType, String lockTime) {
		Assert.hasText(lockTime);
		keyType = keyType == null ? StringUtils.EMPTY : keyType;
		
		if (redis.exists(LOCK_KEY + keyType)) {
			return false;
		} else {
			redis.set(LOCK_KEY + keyType, lockTime);
			
			return true;
		}
	}
	
	/**
	 * 上锁
	 * @param logicType
	 */
	public synchronized void lock(String logicType) {
		//try to get lock
		for(;;) {
			if (isLocked) {
				try {
					throw new Exception("应用程序的锁没有释放,你必须先调用unlock方法释放这个锁");
				} catch (Exception e) {
					logger.error(e.getMessage(),e);
				}
				
				return;
			}
			
			
			long templockTime = System.currentTimeMillis();
			long oldLockedTime = 0;
			String oldLockedTimeStr = getLockValue(logicType);
			if (!oldLockedTimeStr.equals("NULL")) {
				oldLockedTime = Long.valueOf(oldLockedTimeStr);
			} 
			
			//若存在旧锁时间则比较计算该旧锁是否超时，以确保没有死锁
			if(oldLockedTime > 0) {
				if (templockTime - oldLockedTime > dynamicLockTime) {
					//超过最大动态锁定时间了，那么强制释放锁
					delLock(logicType);
					logger.error("强制释放了锁：" + logicType + " 超过了最大动态锁定时间:" + dynamicLockTime);
				}
			}
			
			
			if(setLock(logicType, String.valueOf(templockTime))) {
				isLocked = true;
				break;
			} else {
				try {
					Thread.sleep(10);
				} catch (Exception e) {
					logger.error(e.getMessage(), e);
				}
				
			}
		}
	}
	

	/**
	 * 释放锁
	 * @param logicType
	 */
	public synchronized void unlock(String logicType) {
		long unlockTime = System.currentTimeMillis();
		//自然释放锁时，更新动态自适应最大锁定时间
		long oldLockedTime = 0;
		String oldLockedTimeStr = getLockValue(logicType);
		if (!oldLockedTimeStr.equals("NULL")) {
			oldLockedTime = Long.valueOf(oldLockedTimeStr);
		} 
		
		if(oldLockedTime > 0) {
			if (unlockTime - oldLockedTime > dynamicLockTime) {
				dynamicLockTime = (long) ((unlockTime - oldLockedTime) * 1.5);
			}
		}
		
		isLocked = false;
		//释放锁
		delLock(logicType);
	}
	

	public RedisUtils getRedis() {
		return redis;
	}

	public void setRedis(RedisUtils redis) {
		this.redis = redis;
	}
	

}
